﻿from datetime import datetime, timezone
from secrets import token_urlsafe
import json

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db
from app.models.relationship import Relationship
from app.models.user import User
from app.schemas import relationship as relationship_schema
from app.api.response import success_response
from app.core.redis_client import get_redis

INVITE_CACHE_TTL_SECONDS = 60 * 60 * 24  # 24 小时

router = APIRouter()


def _active_relationship(session: AsyncSession, relationship_id: int | None):
  if not relationship_id:
    return None
  return session.get(Relationship, relationship_id)


@router.get('/me')
async def list_my_relationships(
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  result = await session.scalars(
    select(Relationship).where(
      (Relationship.initiator_id == current_user.id) | (Relationship.partner_id == current_user.id)
    )
  )
  relationships = result.all()
  payload = [
    relationship_schema.RelationshipResponse.model_validate(item, from_attributes=True).model_dump(mode='json')
    for item in relationships
  ]
  return success_response(payload)


@router.post('/invite')
async def create_relationship_invite(
  _: relationship_schema.RelationshipCreate,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  redis = get_redis()
  cache_key = f"relationship:invite:{current_user.id}"

  if redis:
    cached = await redis.get(cache_key)
    if cached:
      try:
        cached_data = json.loads(cached)
      except json.JSONDecodeError:
        cached_data = None
      if cached_data:
        cached_relationship_id = cached_data.get('relationship_id')
        if cached_relationship_id:
          existing_pending = await session.get(Relationship, cached_relationship_id)
          if existing_pending and existing_pending.status == 'pending':
            response = relationship_schema.RelationshipResponse.model_validate(
              existing_pending, from_attributes=True,
            )
            return success_response(response.model_dump(mode='json'))

  existing_active = await session.scalar(
    select(Relationship).where(
      (Relationship.initiator_id == current_user.id) | (Relationship.partner_id == current_user.id),
      Relationship.status == 'active',
    )
  )
  if existing_active:
    raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Active relationship already exists')

  pending_relationship = await session.scalar(
    select(Relationship).where(
      Relationship.initiator_id == current_user.id,
      Relationship.status == 'pending',
    )
  )

  invite_code = token_urlsafe(6)
  if pending_relationship:
    invite = pending_relationship
    invite.invite_code = invite_code
    invite.partner_id = None
    invite.confirmed_at = None
    invite.status = 'pending'
    session.add(invite)
  else:
    invite = Relationship(initiator_id=current_user.id, invite_code=invite_code)
    session.add(invite)

  await session.commit()
  await session.refresh(invite)

  if redis:
    await redis.set(
      cache_key,
      json.dumps({'relationship_id': invite.id, 'invite_code': invite.invite_code}),
      ex=INVITE_CACHE_TTL_SECONDS,
    )

  response = relationship_schema.RelationshipResponse.model_validate(invite, from_attributes=True)
  return success_response(response.model_dump(mode='json'))


@router.post('/join')
async def join_relationship(
  payload: relationship_schema.RelationshipJoin,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  relationship_row = await session.scalar(
    select(Relationship).where(Relationship.invite_code == payload.invite_code)
  )
  if not relationship_row or relationship_row.status != 'pending':
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Invite not found or expired')
  if relationship_row.initiator_id == current_user.id:
    raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Cannot accept your own invite')
  if relationship_row.partner_id and relationship_row.partner_id != current_user.id:
    raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invite already taken')

  relationship_row.partner_id = current_user.id
  relationship_row.status = 'active'
  relationship_row.confirmed_at = datetime.now(tz=timezone.utc)

  initiator = await session.get(User, relationship_row.initiator_id)
  if initiator:
    initiator.active_relationship_id = relationship_row.id
    session.add(initiator)

  current_user.active_relationship_id = relationship_row.id

  session.add(relationship_row)
  session.add(current_user)
  await session.commit()
  await session.refresh(relationship_row)
  response = relationship_schema.RelationshipResponse.model_validate(relationship_row, from_attributes=True)
  return success_response(response.model_dump(mode='json'))


@router.post('/{relationship_id}/leave')
async def leave_relationship(
  relationship_id: int,
  current_user: User = Depends(get_current_user),
  session: AsyncSession = Depends(get_db),
) -> dict:
  relationship_row = await session.get(Relationship, relationship_id)
  if not relationship_row or (
    relationship_row.initiator_id != current_user.id and relationship_row.partner_id != current_user.id
  ):
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Relationship not found')

  partner_user_id = relationship_row.partner_id

  relationship_row.status = 'revoked'
  relationship_row.partner_id = None
  relationship_row.confirmed_at = None

  initiator = await session.get(User, relationship_row.initiator_id)
  if initiator and initiator.active_relationship_id == relationship_row.id:
    initiator.active_relationship_id = None
    session.add(initiator)

  if partner_user_id:
    partner = await session.get(User, partner_user_id)
    if partner and partner.active_relationship_id == relationship_row.id:
      partner.active_relationship_id = None
      session.add(partner)

  if current_user.active_relationship_id == relationship_row.id:
    current_user.active_relationship_id = None

  session.add(relationship_row)
  session.add(current_user)
  await session.commit()
  return success_response()
